package edac;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import bits.BinaryMessage;

/*******************************************************************************
 *     SDR Trunk 
 *     Copyright (C) 2014 Dennis Sheirer
 * 
 *     This program is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 * 
 *     This program is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU General Public License for more details.
 * 
 *     You should have received a copy of the GNU General Public License
 *     along with this program.  If not, see <http://www.gnu.org/licenses/>
 *     -----------------------------------------------------------------------
 *     Galois24 decoder based on Hank Wallace's tutorial/algorithm located at:
 *     http://www.aqdi.com/golay.htm
 ******************************************************************************/

/**
 * Galois 24/12/7 decoder
 */
public class Golay24
{
	private final static Logger mLog = LoggerFactory.getLogger( Golay24.class );

	/**
	 * Galois 24/12 checksums generated by:
	 * 
	 * CRCUtil.generate( 12, 11, 0xC75, 0x0, true );
	 */
	public static final int[] CHECKSUMS = new int[]
	{
	    0x63A, 0x31D, 0x7B4, 0x3DA, 0x1ED, 0x6CC, 0x366, 0x1B3, 
	    0x6E3, 0x54B, 0x49F, 0x475, 0x400, 0x200, 0x100, 0x080, 
	    0x040, 0x020, 0x010, 0x008, 0x004, 0x002, 0x001 
	};

	private static int calculateChecksum( BinaryMessage message, int startIndex )
	{
		int calculated = 0; //Starting value

		/* Iterate the set bits and XOR running checksum with lookup value */
		for (int i = message.nextSetBit( startIndex ); 
				 i >= startIndex && i < startIndex + 12; 
				 i = message.nextSetBit( i+1 ) ) 
		{
			calculated ^= CHECKSUMS[ i - startIndex ];
		}
		
		return calculated;
	}
	
	/**
	 * Performs error detection and returns a corrected copy of the 24-bit
	 * message that starts at the start index.
	 * @param message - source message containing startIndex + 24 bits length
	 * @param startIndex - start of the 24-bit galois 24 protected bit set
	 * @return - corrected 24-bit galois value
	 */
	public static BinaryMessage checkAndCorrect( BinaryMessage message, int startIndex )
	{
		boolean parityError = message.cardinality() % 2 != 0;
		
		int syndrome = getSyndrome( message, startIndex );
		
		/* No errors */
		if( syndrome == 0 )
		{
			if( parityError )
			{
				message.flip( startIndex + 23 );
			}

			message.setCRC( CRC.PASSED );
			
			return message;
		}

		/* Get original message value */
		int original = message.getInt( 0, 22 );
		
		int index = -1;
		int syndromeWeight = 3;
		int errors = 0;
		
		while( index < 23 )
		{
			if( index != -1 )
			{
				/* restore the previous flipped bit */
				if( index > 0 )
				{
					message.flip( index - 1 );
				}
				
				message.flip( index );
				
				syndromeWeight = 2;
			}
			
			syndrome = getSyndrome( message, startIndex );
			
			if( syndrome > 0 )
			{
				for( int i = 0; i < 23; i++ )
				{
				
					errors = Integer.bitCount( syndrome );
					
					if( errors <= syndromeWeight )
					{
						message.xor( 12, 11, syndrome );
						
						message.rotateRight( i, startIndex, startIndex + 22 );

						if( index >= 0 )
						{
							errors ++;
						}

						int corrected = message.getInt( 0, 22 );
						
						if( Integer.bitCount( original ^ corrected ) > 3 )
						{
							message.setCRC( CRC.FAILED_CRC );
							
							return message;
						}
						
						message.setCRC( CRC.PASSED );
						
						return message;
					}
					else
					{
						message.rotateLeft( startIndex, startIndex + 22 );
						syndrome = getSyndrome( message, startIndex );
					}
				}
				
				index++;
			}
		}

		message.setCRC( CRC.FAILED_CRC );
		
		return message;
	}

	private static int getSyndrome( BinaryMessage message, int startIndex )
	{
		int calculated = calculateChecksum( message, startIndex );
		
		int checksum = message.getInt( startIndex + 12, startIndex + 22 );

		return ( checksum ^ calculated );
	}
}
